package hu.myai.gui.widgets.graph;

import hu.myai.api.indicators.IndicatorConsumerInterface;
import hu.myai.api.triggers.TriggerConsumerInterface;
import hu.myai.gui.widgets.model.IPartitionable;
import hu.myai.gui.widgets.model.IScalar;
import hu.myai.gui.widgets.model.IScalarListener;
import hu.myai.gui.widgets.model.Partition;
import hu.myai.gui.widgets.model.Partition.Interval;
import hu.myai.model.IndicatorVO;
import hu.myai.model.TriggerVO;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;

public class ScalarGraph extends Composite implements IScalarListener, IPartitionable, TriggerConsumerInterface, IndicatorConsumerInterface {

	private IScalar scalar;
	private double min = 1.3;
	private double max = 1.5;

	private boolean panEnabled = true;
	private boolean zoomEnabled = true;
	private double zoomPercent = 10.0;

	// partition
	private Partition partition;
	// grid
	private IGraphGrid grid;
	private boolean gridVisible = true;
	// mouse
	private boolean mouseDown;
	private double omin, omax;
	int mx, my;

	public ScalarGraph(Composite parent, int style) {
		super(parent, style);
		this.addPaintListener(new PaintListener() {
			@Override
			public void paintControl(PaintEvent e) {
				onPaint(e);
			}
		});
		this.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseDown(MouseEvent e) {
				mouseDown = true;
				omin = min;
				omax = max;
				mx = e.x;
				my = e.y;
			}

			@Override
			public void mouseUp(MouseEvent e) {
				mouseDown = false;
			}
		});

		this.addMouseWheelListener(new MouseWheelListener() {
			@Override
			public void mouseScrolled(MouseEvent e) {
				if (e.count > 0) {
					zoomIn();
				} else if (e.count < 0) {
					zoomOut();
				}
			}
		});

		this.addMouseMoveListener(new MouseMoveListener() {
			@Override
			public void mouseMove(MouseEvent e) {
				if (mouseDown) {
					pan(e.y);
				}
			}
		});

		this.addMouseTrackListener(new MouseTrackAdapter() {
			@Override
			public void mouseHover(MouseEvent e) {
				updateToolTip(e);
			}
		});
	}

	private void onPaint(PaintEvent e) {
		if (scalar != null) {
			int w = this.getSize().x;
			int h = this.getSize().y;

			// draw partitions
			if ((partition != null) && (partition.getIntervals().size() > 0)) {
				Interval[] intervals = partition.getIntervals().toArray(new Interval[0]);

				for (int i = 0; i < intervals.length; i++) {
					Interval it = intervals[i];
					double v0 = it.getStart();
					double v1 = Double.POSITIVE_INFINITY;
					if ((i + 1) < intervals.length) {
						v1 = intervals[i + 1].getStart();
					}
					if ((v1 >= min) && (v0 <= max)) {
						v0 = Math.max(v0, min);
						v1 = Math.min(v1, max);
						int h0 = (int) ((h - 1) * (max - v0) / (max - min));
						int h1 = (int) ((h - 1) * (max - v1) / (max - min));
						e.gc.setBackground(new Color(e.display, it.getColorHint()[0], it.getColorHint()[1], it.getColorHint()[2]));
						e.gc.fillRectangle(0, h1, w, h0 - h1 + 1);
					}
				}
			}

			// draw graph
			int n = scalar.getDepth();
			Map<Integer, Integer> mapX = new HashMap<Integer, Integer>();
			Map<Integer, Integer> mapY = new HashMap<Integer, Integer>();

			// Get a green system color object - we don't
			// need to dispose that
			for (int i = 0; i < n; i++) {
				int color = scalar.getColor(n - 1 - i);
				int style = scalar.getStyle(n - 1 - i);

				Color fg = e.display.getSystemColor(color);
				e.gc.setForeground(fg);
				int px = (i * (w - 1)) / (n - 1);
				int py = (int) (((max - scalar.getValue(n - 1 - i)) * (h - 1)) / (max - min));
				if (mapX.containsKey(color) && mapY.containsKey(color)) {
					if (style == 0)
						e.gc.drawPoint(px, py);
					if (style == 1)
						e.gc.drawLine(mapX.get(color), mapY.get(color), px, py);
					if (style == -1)
						e.gc.drawLine(px, py - 10, px, py - 15);
					if (style == -2)
						e.gc.drawLine(px, py + 10, px, py + 15);
				}
				mapX.put(color, px);
				mapY.put(color, py);
			}

			if ((grid != null) && gridVisible) {
				grid.paintGrid(e);
			}
		}
	}

	private void zoomIn() {
		if (zoomEnabled) {
			double c = (max + min) / 2.0;
			double r = (max - min) / 2.0;
			double zp = 1 + zoomPercent / 100.0;
			max = c + r / zp;
			min = c - r / zp;
			reDraw();
		}
	}

	private void zoomOut() {
		if (zoomEnabled) {
			double c = (max + min) / 2.0;
			double r = (max - min) / 2.0;
			double zp = 1 + zoomPercent / 100.0;
			max = c + r * zp;
			min = c - r * zp;
			reDraw();
		}
	}

	private void pan(int y) {
		if (panEnabled) {
			int dy = y - my;
			double dc = dy * (omax - omin) / this.getSize().y;

			double c = (omax + omin) / 2.0;
			double r = (omax - omin) / 2.0;

			c += dc;
			max = c + r;
			min = c - r;
			reDraw();
		}
	}

	@Override
	public void depthChanged(IScalar source, int oldDepth, int newDepth) {
		reDraw();
	}

	@Override
	public void valueChanged(IScalar source, double oldValue, double newValue) {
		reDraw();
	}

	// getters / setters
	public IScalar getScalar() {
		return scalar;
	}

	public void setScalar(IScalar scalar) {
		if (this.scalar != null) {
			this.scalar.removeScalarListener(this);
		}
		this.scalar = scalar;
		if (this.scalar != null) {
			this.scalar.addScalarListener(this);
		}
	}

	public double getMin() {
		return min;
	}

	public void setMin(double min) {
		this.min = min;
	}

	public double getMax() {
		return max;
	}

	public void setMax(double max) {
		this.max = max;
	}

	@Override
	public Partition getPartition() {
		return partition;
	}

	@Override
	public void setPartition(Partition partition) {
		this.partition = partition;
	}

	public boolean isGridVisible() {
		return gridVisible;
	}

	public void setGridVisible(boolean gridVisible) {
		this.gridVisible = gridVisible;
	}

	public IGraphGrid getGrid() {
		return grid;
	}

	public void setGrid(IGraphGrid grid) {
		this.grid = grid;
	}

	public boolean isPanEnabled() {
		return panEnabled;
	}

	public void setPanEnabled(boolean panEnabled) {
		this.panEnabled = panEnabled;
	}

	public boolean isZoomEnabled() {
		return zoomEnabled;
	}

	public void setZoomEnabled(boolean zoomEnabled) {
		this.zoomEnabled = zoomEnabled;
	}

	private void updateToolTip(MouseEvent e) {
		if (partition != null) {
			double value = max + ((min - max) * e.y) / (this.getSize().y - 1);
			Interval i = partition.getIntervalContainingValue(value);
			if (i != null) {
				this.setToolTipText(i.getName());
			} else
				this.setToolTipText("NORMAL");
		}
	}

	public void autoZoom() {
		if (scalar == null)
			return;

		double min = Double.POSITIVE_INFINITY;
		double max = Double.NEGATIVE_INFINITY;

		for (int i = 0; i < scalar.getDepth(); i++) {
			double v = scalar.getValue(i);
			min = Math.min(min, v);
			max = Math.max(max, v);
		}
		this.min = min;
		this.max = max;
		reDraw();
	}

	Map<String, Integer> colorMap = new HashMap<String, Integer>();
	int[] colors = { SWT.COLOR_BLUE, SWT.COLOR_RED, SWT.COLOR_GREEN, SWT.COLOR_DARK_YELLOW, SWT.COLOR_DARK_GRAY, SWT.COLOR_DARK_RED, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
			34, 35, 36, 37 };
	int i = 0;

	@Override
	public void addIndicator(String indicatorName, IndicatorVO indicatorValue) {
		if (scalar != null) {
			if (!colorMap.containsKey(indicatorName)) {
				colorMap.put(indicatorName, colors[i++]);
			}

			if (indicatorValue == null) {
				// scalar.setValue(null);
				// scalar.setColor(0);
			} else {
				scalar.setValue(indicatorValue.getValue(), colorMap.get(indicatorName), 1);
			}
			// System.out.println(indicatorName);
		}
		autoZoom();
	}

	private void reDraw() {
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
		}
		getDisplay().asyncExec(new Runnable() {
			public void run() {
				if (!isDisposed()) {
					redraw();
				}
			}
		});
	}

	@Override
	public void addTrigger(String triggerName, TriggerVO triggerValue) {
		if (scalar != null) {
			if (!colorMap.containsKey(triggerName)) {
				colorMap.put(triggerName, colors[i++]);
			}
			int style;

			if (triggerValue != null) {

				if (triggerValue.getTradeType() > 0) {
					style = -1;
					scalar.setValue(triggerValue.getTick().getHigh(), colorMap.get(triggerName), style);
				} else {
					style = -2;
					scalar.setValue(triggerValue.getTick().getLow(), colorMap.get(triggerName), style);
				}
			}
			// System.out.println(triggerName);
		}
		autoZoom();
	}

	@Override
	public boolean addIndicatorNameForCheck(String nameForCheck) {
		return true;
	}

	@Override
	public boolean addTriggerNameForCheck(String nameForCheck) {
		return true;
	}

	@Override
	public boolean removeTriggerNameForCheck(String nameForCheck) {
		return true;
	}

	@Override
	public boolean removeIndicatorNameForCheck(String nameForCheck) {
		return true;
	}

	@Override
	public String getName() {
		return null;
	}
}